//#define LOG_NDEBUG 0
#define LOG_TAG "MpiJpegEncoder"
#include <utils/Log.h>

#include <stdlib.h>
#include <string.h>
#include "mpp_err.h"
#include "Utils.h"
#include "version.h"
#include "MpiJpegEncoder.h"
#include "RKEncoderWraper.h"

#define DEBUG_TIMING
//#define DEBUG_DUMP_FILE

#define _ALIGN(x, a)         (((x)+(a)-1)&~((a)-1))

typedef struct {
    struct timeval start;
    struct timeval end;
} DebugTimeInfo;

static DebugTimeInfo time_info;

static void time_start_record()
{
#ifdef DEBUG_TIMING
    gettimeofday(&time_info.start, NULL);
#endif
}

static void time_end_record(const char *task)
{
#ifdef DEBUG_TIMING
    gettimeofday(&time_info.end, NULL);
    ALOGD("%s consumes %ld ms", task,
          (time_info.end.tv_sec  - time_info.start.tv_sec)  * 1000 +
          (time_info.end.tv_usec - time_info.start.tv_usec) / 1000);
#endif
}

MpiJpegEncoder::MpiJpegEncoder() :
    mpp_ctx(NULL),
    mpi(NULL),
    initOK(0),
    mInputWidth(0),
    mInputHeight(0),
    mFrameGroup(NULL),
    fp_input(NULL),
    fp_output(NULL)
{
    ALOGI("version - %s", GIT_INFO);

    /* Input Format set to YUV420SP default */
    mInputFmt = INPUT_FMT_YUV420SP;

#ifdef DEBUG_DUMP_FILE
    fp_input = fopen("/data/enc_input.yuv", "wb+");
    if (fp_input > 0) {
        ALOGD("start dump input yuv to /data/enc_input.yuv");
    }

    fp_output = fopen("/data/enc_output.jpg", "wb+");
    if (fp_output > 0) {
        ALOGD("start dump output jpeg to /data/enc_output.jpg");
    }
#endif
}

MpiJpegEncoder::~MpiJpegEncoder()
{
    if (mpp_ctx) {
        mpp_destroy(mpp_ctx);
        mpp_ctx = NULL;
    }
    if (mFrameGroup) {
        mpp_buffer_group_put(mFrameGroup);
        mFrameGroup = NULL;
    }

    if (fp_input != NULL) {
        fclose(fp_input);
    }
    if (fp_output != NULL) {
        fclose(fp_output);
    }
}

bool MpiJpegEncoder::prepareEncoder()
{
    MPP_RET ret = MPP_OK;

    if (initOK)
        return true;

    /* Input frame buffer group */
    mpp_buffer_group_get_internal(&mFrameGroup, MPP_BUFFER_TYPE_ION);
    mpp_buffer_group_limit_config(mFrameGroup, 0, 5);

    ret = mpp_create(&mpp_ctx, &mpi);
    if (MPP_OK != ret) {
        ALOGE("failed to create mpp context");
        return false;
    }

    ret = mpp_init(mpp_ctx, MPP_CTX_ENC, MPP_VIDEO_CodingMJPEG);
    if (MPP_OK != ret) {
        ALOGE("failed to init mpp");
        return false;
    }

    initOK = 1;

    return true;
}

void MpiJpegEncoder::flushBuffer()
{
    if (mpi && initOK) {
        mpi->reset(mpp_ctx);
    }
}

bool MpiJpegEncoder::updateEncodeCfg(int width, int height, InputFormat fmt)
{
    MPP_RET ret = MPP_OK;

    MppEncPrepCfg prep_cfg;
    MppEncCodecCfg codec_cfg;

    int hor_stride, ver_stride;

    if (!initOK) {
        ALOGW("Please prepare encoder first before updateEncodeCfg");
        return false;
    }

    ALOGV("updateEncodeCfg - %dx%d - inputFmt: %d", width, height, fmt);

    if (width < 16 || width > 8192) {
        ALOGE("invalid width %d is not in range [16..8192]", width);
        return false;
    }

    if (height < 16 || height > 8192) {
        ALOGE("invalid height %d is not in range [16..8192]", height);
        return false;
    }

    hor_stride = _ALIGN(width, 16);
    ver_stride = _ALIGN(height, 8);

    prep_cfg.change        = MPP_ENC_PREP_CFG_CHANGE_INPUT |
                             MPP_ENC_PREP_CFG_CHANGE_ROTATION |
                             MPP_ENC_PREP_CFG_CHANGE_FORMAT;
    prep_cfg.width         = width;
    prep_cfg.height        = height;
    prep_cfg.hor_stride    = hor_stride;
    prep_cfg.ver_stride    = ver_stride;
    prep_cfg.format        = (MppFrameFormat)fmt;
    prep_cfg.rotation      = MPP_ENC_ROT_0;
    ret = mpi->control(mpp_ctx, MPP_ENC_SET_PREP_CFG, &prep_cfg);
    if (MPP_OK != ret) {
        ALOGE("mpi control enc set prep cfg failed ret %d", ret);
        return false;
    }

    codec_cfg.coding = MPP_VIDEO_CodingMJPEG;
    codec_cfg.jpeg.change  = MPP_ENC_JPEG_CFG_CHANGE_QP;
    codec_cfg.jpeg.quant   = 6;

    ret = mpi->control(mpp_ctx, MPP_ENC_SET_CODEC_CFG, &codec_cfg);
    if (MPP_OK != ret) {
        ALOGE("mpi control enc set codec cfg failed ret %d", ret);
        return false;
    }

    mInputWidth = width;
    mInputHeight = height;
    mInputFmt = fmt;

    return true;
}

bool MpiJpegEncoder::encodeFrame(char *data, OutputPacket_t *aPktOut)
{
    MPP_RET ret         = MPP_OK;
    /* input frame and output packet */
    MppFrame frame      = NULL;
    MppPacket packet    = NULL;
    MppBuffer frm_buf   = NULL;
    void *frm_ptr       = NULL;

    int frame_size      = 0;

    if (!initOK) {
        ALOGW("Please prepare encoder first before encodeFrame");
        return false;
    }

    time_start_record();

    int hor_stride = _ALIGN(mInputWidth, 16);
    int ver_stride = _ALIGN(mInputHeight, 16);

    // get input frame size
    if (mInputFmt <= MPP_FMT_YUV420SP_VU)
        frame_size = hor_stride * ver_stride * 3 / 2;
    else if (mInputFmt <= MPP_FMT_YUV422_UYVY) {
        // NOTE: yuyv and uyvy need to double stride
        hor_stride *= 2;
        frame_size = hor_stride * ver_stride;
    } else
        frame_size =hor_stride * ver_stride * 4;

    ret = mpp_buffer_get(mFrameGroup, &frm_buf, frame_size);
    if (MPP_OK != ret) {
        ALOGE("failed to get buffer for input frame ret %d", ret);
        goto ENCODE_OUT;
    }

    frm_ptr = mpp_buffer_get_ptr(frm_buf);

    // NOTE: The hardware vpu only process buffer aligned, so we translate
    // input frame to aligned before encode
    ret = read_yuv_image((uint8_t*)frm_ptr, (uint8_t*)data,
                         mInputWidth, mInputHeight, hor_stride,
                         ver_stride, (MppFrameFormat)mInputFmt);
    if (MPP_OK != ret)
        goto ENCODE_OUT;

    ret = mpp_frame_init(&frame); /* input frame */
    if (MPP_OK != ret) {
        ALOGE("failed to init input frame");
        goto ENCODE_OUT;
    }

    mpp_frame_set_width(frame, mInputWidth);
    mpp_frame_set_height(frame, mInputWidth);
    mpp_frame_set_hor_stride(frame, hor_stride);
    mpp_frame_set_ver_stride(frame, ver_stride);
    mpp_frame_set_fmt(frame, (MppFrameFormat)mInputFmt);
    mpp_frame_set_buffer(frame, frm_buf);

    /* dump input frame at fp_input */
    dump_mpp_frame_to_file(frame, fp_input);

    ret = mpi->encode_put_frame(mpp_ctx, frame);
    if (MPP_OK != ret) {
        ALOGE("failed to put input frame");
        goto ENCODE_OUT;
    }

    ret = mpi->encode_get_packet(mpp_ctx, &packet);
    if (MPP_OK != ret) {
        ALOGE("failed to get output packet");
        goto ENCODE_OUT;
    }

    if (packet) {
        memset(aPktOut, 0, sizeof(OutputPacket_t));

        aPktOut->data = (uint8_t*)mpp_packet_get_pos(packet);
        aPktOut->size = mpp_packet_get_length(packet);

        /* dump output packet at fp_output */
        dump_mpp_packet_to_file(packet, fp_output);

        mpp_packet_deinit(&packet);

        ALOGV("encoded one frame get output size %d", aPktOut->size);
    }

ENCODE_OUT:
    if (frm_buf) {
        mpp_buffer_put(frm_buf);
        frm_buf = NULL;
    }

    time_end_record("encode frame");

    return ret == MPP_OK ? true : false;
}

bool MpiJpegEncoder::encodeFile(const char *input_file, const char *output_file)
{
    MPP_RET ret = MPP_OK;
    char *buf = NULL;
    size_t buf_size = 0;

    OutputPacket_t pktOut;

    ALOGD("mpi_jpeg_enc encodeFile start with cfg %dx%d inputFmt %d",
          mInputWidth, mInputHeight, mInputFmt);

    ret = get_file_ptr(input_file, &buf, &buf_size);
    if (MPP_OK != ret)
        goto ENCODE_OUT;

    if (!encodeFrame(buf, &pktOut)) {
        ALOGE("failed to encode input frame");
        goto ENCODE_OUT;
    }

    // Write output packet to destination.
    ret = dump_ptr_to_file((char*)pktOut.data, pktOut.size, output_file);
    if (MPP_OK != ret)
        ALOGE("failed to dump packet to file %s", output_file);

    flushBuffer();

    ALOGD("JPEG encode success get output file %s with size %d",
          output_file, pktOut.size);

ENCODE_OUT:
    if (buf)
        free(buf);

    return ret == MPP_OK ? true : false;
}

bool MpiJpegEncoder::encodeTask(JpegEncTask task)
{
    MPP_RET ret         = MPP_OK;
    MppTask mpp_task    = NULL;

    uint32_t width      = 0;
    uint32_t height     = 0;
    uint32_t h_stride   = 0;
    uint32_t v_stride   = 0;
    uint32_t input_size = 0;

    /* input frame and output packet */
    MppFrame frame      = NULL;
    MppBuffer frm_buf   = NULL;
    MppPacket packet    = NULL;

    if (!initOK) {
        ALOGW("Please prepare encoder first before encodeTask");
        return false;
    }

    time_start_record();

    if (mInputWidth != task.inputWidth || mInputHeight != task.inputHeight) {
        /* JpegEncTask set default yuv420sp output */
        bool success = updateEncodeCfg(task.inputWidth, task.inputHeight,
                                       INPUT_FMT_YUV420SP);
        if (!success) {
            ALOGE("failed to update encodeCfg.");
            return false;
        }
    }

    width      = task.inputWidth;
    height     = task.inputHeight;
    h_stride   = _ALIGN(width, 16);
    v_stride   = _ALIGN(height, 8);
    input_size = width * height * 3 / 2;

    ALOGV("start encode JpegTask frame-%dx%d input-size-%d",
          width, height, input_size);

    /* setup input frame */
    ret = mpp_frame_init(&frame);
    if (MPP_OK != ret) {
        ALOGE("mpp_frame_init failed");
        goto ENCODE_OUT;
    }

    mpp_frame_set_width(frame, width);
    mpp_frame_set_height(frame, height);
    mpp_frame_set_hor_stride(frame, h_stride);
    mpp_frame_set_ver_stride(frame, v_stride);
    mpp_frame_set_fmt(frame, (MppFrameFormat)mInputFmt);

    /* try import input fd to vpu */
    MppBufferInfo inputCommit;
    memset(&inputCommit, 0, sizeof(MppBufferInfo));
    inputCommit.type = MPP_BUFFER_TYPE_ION;
    inputCommit.size = input_size;
    inputCommit.fd = task.inputPhyAddr;

    ret = mpp_buffer_import(&frm_buf, &inputCommit);
    if (MPP_OK != ret) {
        ALOGE("failed to import input pictrue buffer");
        goto ENCODE_OUT;
    }
    mpp_frame_set_buffer(frame, frm_buf);

    /* dump input frame at fp_input */
    dump_mpp_frame_to_file(frame, fp_input);

    /* put input frame */
    ret = mpi->encode_put_frame(mpp_ctx, frame);
    if (MPP_OK != ret) {
        ALOGE("failed to put input frame");
        goto ENCODE_OUT;
    }

    /* get output packet */
    ret = mpi->encode_get_packet(mpp_ctx, &packet);
    if (MPP_OK != ret) {
        ALOGE("failed to get output packet");
        goto ENCODE_OUT;
    }

    if (packet) {
        uint8_t *data = (uint8_t*)mpp_packet_get_pos(packet);
        uint32_t len = mpp_packet_get_length(packet);

        /* dump output packet at fp_output */
        dump_mpp_packet_to_file(packet, fp_output);

        mpp_packet_deinit(&packet);
        ALOGV("encoded one task get output size %d", len);
    }

ENCODE_OUT:
    if (frm_buf) {
        mpp_buffer_put(frm_buf);
        frm_buf = NULL;
    }

    time_end_record("decode task");

    return ret == MPP_OK ? true : false;;
}